package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"logprocess/model"
	"logprocess/pkg/setting"
	"net/url"
	"os"
	"os/signal"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type Reader interface {
	Read(rc chan []byte)
}

type Writer interface {
	Write(wc chan *Message)
}

type LogProcess struct {
	rc    chan []byte
	wc    chan *Message
	read  Reader
	write Writer
}

type ReadFromFile struct {
	path string //  读取文件的路径
}

func (r *ReadFromFile) Read(rc chan []byte) {
	// 读取模块
	f, err := os.Open(r.path)
	if err != nil {
		panic(fmt.Sprintf("Open file error:%s", err.Error()))
	}

	// 从文件末尾开始逐行读取文件内容
	f.Seek(0, 2)
	rd := bufio.NewReader(f)
	for {
		line, err := rd.ReadBytes('\n')
		if err == io.EOF {
			time.Sleep(500 * time.Microsecond)
			continue
		} else if err != nil {
			panic(fmt.Sprintf("ReadBytes file error:%s", err.Error()))
		}
		rc <- line[:len(line)-1]
	}

}

type WriteToDB struct {
}

func (w *WriteToDB) Write(wc chan *Message) {
	// 写入模块
	for v := range wc {
		accessLog := &model.AccessLog{
			TimeLocal: v.TimeLocal,
			BytesSent: v.BytesSent,
			Ip:        v.IP,
			Path:      v.Path,
			Method:    v.Method,
			Status:    v.Status,
			UserAgent: v.UserAgent,
		}
		accessLog.Create()
	}
}

type Message struct {
	TimeLocal                           time.Time
	BytesSent                           int
	IP, Path, Method, Status, UserAgent string
}

func (l *LogProcess) Process() {
	//124.158.157.90 - - [30/Aug/2021:22:10:04 +0800] "GET / HTTP/1.1" 200 1873 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7" "-"
	//([\d\.]+)\s+([^ \[]+)\s+([^ \[]+)\s+\[([^\]]+)\]\s+([a-z]+)\s+\"([^"]+)\"\s+(\d{3})\s+(\d+)\s+\"([^"]+)\"\s+\"(.*?)\"\s+\"([\d\.-]+)\"\s+([\d\.-]+)\s+([\d\.-]+)
	r := regexp.MustCompile(setting.AppSetting.RegexpPattern)

	loc, _ := time.LoadLocation(setting.AppSetting.TimeLocation)

	// 解析模块
	for v := range l.rc {
		ret := r.FindStringSubmatch(string(v))

		if len(ret) != 10 {
			log.Println("FindStringSubmatch fail:", string(v))
			continue
		}

		reqSli := strings.Split(ret[5], " ")
		if len(reqSli) != 3 {
			log.Println("strings.Split fail:", ret[6])
			continue
		}

		u, err := url.Parse(reqSli[1])
		if err != nil {
			log.Println("url.Parse fail:", err)
			continue
		}

		var b bool
		excludes := strings.Split(setting.AppSetting.Excludes, "|")
		for _, exclude := range excludes {
			if strings.Contains(u.Path, exclude) {
				b = true
				break
			}
		}

		// 过滤特殊路径
		if b {
			continue
		}

		message := &Message{}
		// IP
		message.IP = ret[1]
		// time
		t, err := time.ParseInLocation(setting.AppSetting.TimeParseLayout, ret[4], loc)
		if err != nil {
			log.Println("ParseInLocation fail:", err.Error(), ret[4])
			continue
		}
		message.TimeLocal = t

		byteSent, _ := strconv.Atoi(ret[7])
		message.BytesSent = byteSent

		message.Method = reqSli[0]

		message.Path = u.Path

		message.Status = ret[6]

		message.UserAgent = ret[9]

		l.wc <- message
	}
}

func init() {
	setting.Setup()
	model.Setup()
}

func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	r := &ReadFromFile{path: setting.AppSetting.AccessLogPath}
	w := &WriteToDB{}

	lp := &LogProcess{
		rc:    make(chan []byte),
		wc:    make(chan *Message),
		read:  r,
		write: w,
	}

	go lp.read.Read(lp.rc)
	go lp.Process()
	go lp.write.Write(lp.wc)

	// wait
	<-sig
}
